-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement TLS support for incoming redis connections #141
Conversation
Looks good! I think supporting different keys for the listener and upstream totally makes sense (though it makes me have terrible thoughts about yaml and templating...). cc @johndelcastillo whats your feel on the key formats / having to specify a pub and private as seperate files. Some formats will allow us to just put both in a single file (e.g. pem iirc). Additionally we should allow (irrespective of where we land above) the ability to configure a store of ca certs to trust when building the connection. That way we will have the ability to configure:
From a PCI DSS perspective we may need to also be able to allow/deny specific TLS algorithms or modes?? @johndelcastillo ? cc @Claudenw -> Any input on cert config ergonomics? |
|
Update on how this is going:
I'll probably try one of the last 2 hacky solutions and see how it goes. Edit: |
Doing async tests isn't so bad with the tokio macro annotations (other tests have them) and it largely doesn't create additional complexity. |
It turns out that rustls, the TLS library I am using, does not support connecting to servers via an IP address, it only supports connecting via a domain name. Is IP address connections a production requirement? Because rustls is pure rust, using it greatly simplifies building and deploying. |
tl;dr yup go ahead with tokio-native-tls as it is indeed a req to connect via IP long version: From a security perspective it would be great if we started leveraging certs for host identity but i know we don't atm. @rukai we can use prod instaclustr redis deployments for testing if needed (we even have a terraform provider to help setup infra for tests). |
I have now discovered that the native-tls crate only supports PKCS#12 sfackler/rust-native-tls#27 Should I try looking for another library? |
Looks like openssl covers all that we need (pem and ip address connections) so I'll go down that path for now https://docs.rs/openssl/0.10.36/openssl/ssl/index.html |
Made a lot of progress but also hit a very complicated issue. However when I try to connect via openssl in the integration test it panics with: I can reproduce this issue in the tiny test used in tokio-openssl when modified to use the keys in the way redis uses them. But when I change it to use certificates in the same way redis does, I get the same error I am getting in shotover: Using openssl has the nice advantage that I can look into what openssl calls redis and redis-cli are making and compare them to what shotover and the integration test are doing. Tomorrow I will try compiling shotover with the same version of openssl that redis is using and see if that helps. |
Double check the certificates we are using can be verified (if they are the ones pulled directly from redis examples should be ok, or if they can be used with redis set to verify without shotover in the middle). Double check things like the Subject name in the cert. Otherwise it looks like the error is in how we are setting up the openssl context to verify the peer / connection. From a cursory glance at We also appear to setting the tls security to modern which is slightly stricter than intermediate. What profile does the redis server use (if it matches one at all)? |
Great stuff! So maybe double check if Redis has a cert mode of operation that will verify things (could be config), otherwise this is one of those wonderful scenarios where shotover aims to be protocol and implementation compatible... bugs and all. Practically to move forward.
|
The PR is now ready for review! The integration test is pretty barebones because I cant access all the helper methods as they are dont work with an async connection.
I have left a warning in to remind us to continue investigating the TLS issue after this is merged. |
lgtm! |
pub fn new(tls_config: TlsConfig) -> Result<TlsAcceptor> { | ||
let mut builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls())?; | ||
builder.set_ca_file(tls_config.certificate_authority_path)?; | ||
builder.set_private_key_file(tls_config.private_key_path, SslFiletype::PEM)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should happen if these files are malformed? I think it should be similar to how we handle other malformed or invalid data in the topology.yaml
config file.
}) | ||
} | ||
|
||
pub async fn connect(&self, tcp_stream: TcpStream) -> Result<SslStream<TcpStream>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should happen when the TLS connection fails? I changed the key file to cause this and test_tls
still passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this is actually more relevant to your other comment.
The base64 was invalid and this is how the error is handled.
I'll try to investigate what happens if I modify the key to be incorrect not just malformed.
I'll also check if modifying the key like this affects redis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What did you change to get that output?
Edit: Oh I see now: "U" to "0"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its in the diff in the screenshot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its actually a deleted character which is why it failed to parse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@conorbros what exactly did you change and how did it pass? 😮
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed one of the characters in the key file.
Since using TLS with Redis can add big performance overheads perhaps we should add another benchmark. |
I'm thinking we can add benchmarks in a follow up PR. |
let ssl = self | ||
.connector | ||
.configure()? | ||
.verify_hostname(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense given the lack of hostname support in the context of cluster mode: redis/redis#2186
We can connect to a node by hostname to discover other nodes, but the other nodes are only referenced by IP, so unless you require reverse DNS records (with custom verifier), we don't have the hostnames to check.
We mitigate this for Cassandra by including iPAddress
(and dNSName
) in the subjectAltName
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooh interesting, I added your comment to #167
@XA21X I have addressed your feedback |
There has been only minor changes since bens review, so im going to go ahead and merge 🎉 |
Sorry I am so late to this conversation. But I have a few questions.
|
|
OK, I just want to be sure that we are not dependant upon them for anything else. Also, they will need to be replaced when they age out.
https://docs.rs/openssl/0.10.29/openssl/ssl/struct.SslAcceptorBuilder.html has a series of functions to set cipher suites, set the cipher list, set options as defined in https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_options.html and similar. So there are a lot of knobs and moving parts. If we are probably going to need a way to set any/all of those. Though perhaps not right now. Does rust have a standard way to map a yaml file to properties on a struct? |
Design
The plan is to specify paths to keys and certs like this:
I think repeating the same paths when we are just doing a simple pass through like this is fine.
It keeps the configuration format simple and allows flexibility of seperate keys between incoming and outgoing connections and even between different sources and outgoing endpoints.
Open to other ideas though.
This PR
But the scope of this PR is to just implement redis support for incoming TLS connections.
After this PR I will do two more PRs for:
Questions
I have used the key/certificate formats as described here https://redis.io/topics/encryption
Are there any other specific key/certificate formats I should/need to support and test with?
TODO
Currently the redis-tls/redis-cli.sh script can be used to create a connection when shotover is configured to use redis tls.
But I still need to do the following: